/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * ViaAnnotationsReportReader.java
 * Copyright (C) 2018-2020 University of Waikato, Hamilton, NZ
 */

package adams.data.io.input;

import adams.core.io.FileUtils;
import adams.data.report.Report;
import adams.flow.transformer.locateobjects.LocatedObject;
import adams.flow.transformer.locateobjects.LocatedObjects;
import adams.flow.transformer.locateobjects.ObjectPrefixHandler;
import adams.gui.visualization.image.ObjectLocationsOverlayFromReport;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;

import java.awt.Polygon;
import java.awt.Rectangle;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

/**
 <!-- globalinfo-start -->
 * Interprets polygon annotations generated by VGG Image Annotator.<br>
 * For more information, see:<br>
 * http:&#47;&#47;www.robots.ox.ac.uk&#47;~vgg&#47;software&#47;via&#47;
 * <br><br>
 <!-- globalinfo-end -->
 *
 <!-- options-start -->
 * <pre>-logging-level &lt;OFF|SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST&gt; (property: loggingLevel)
 * &nbsp;&nbsp;&nbsp;The logging level for outputting errors and debugging output.
 * &nbsp;&nbsp;&nbsp;default: WARNING
 * </pre>
 *
 * <pre>-input &lt;adams.core.io.PlaceholderFile&gt; (property: input)
 * &nbsp;&nbsp;&nbsp;The file to read and turn into a report.
 * &nbsp;&nbsp;&nbsp;default: ${CWD}
 * </pre>
 *
 * <pre>-prefix &lt;java.lang.String&gt; (property: prefix)
 * &nbsp;&nbsp;&nbsp;The report field prefix used in the report.
 * &nbsp;&nbsp;&nbsp;default: Object.
 * </pre>
 *
 * <pre>-label-key &lt;java.lang.String&gt; (property: labelKey)
 * &nbsp;&nbsp;&nbsp;The key in the meta-data containing the label, ignored if empty.
 * &nbsp;&nbsp;&nbsp;default:
 * </pre>
 *
 <!-- options-end -->
 *
 * @author FracPete (fracpete at waikato dot ac dot nz)
 */
public class ViaAnnotationsReportReader
  extends AbstractReportReader<Report>
  implements ObjectPrefixHandler {

  private static final long serialVersionUID = 5716807404370681434L;

  /** the prefix of the objects in the report. */
  protected String m_Prefix;

  /** the meta-data key with the label. */
  protected String m_LabelKey;

  /**
   * Returns a string describing the object.
   *
   * @return 			a description suitable for displaying in the gui
   */
  @Override
  public String globalInfo() {
    return "Interprets polygon annotations generated by VGG Image Annotator.\n"
      + "For more information, see:\n"
      + "http://www.robots.ox.ac.uk/~vgg/software/via/";
  }

  /**
   * Adds options to the internal list of options.
   */
  @Override
  public void defineOptions() {
    super.defineOptions();

    m_OptionManager.add(
      "prefix", "prefix",
      ObjectLocationsOverlayFromReport.PREFIX_DEFAULT);

    m_OptionManager.add(
      "label-key", "labelKey",
      "");
  }

  /**
   * Sets the field prefix used in the report.
   *
   * @param value 	the field prefix
   */
  public void setPrefix(String value) {
    m_Prefix = value;
    reset();
  }

  /**
   * Returns the field prefix used in the report.
   *
   * @return 		the field prefix
   */
  public String getPrefix() {
    return m_Prefix;
  }

  /**
   * Returns the tip text for this property.
   *
   * @return 		tip text for this property suitable for
   * 			displaying in the GUI or for listing the options.
   */
  public String prefixTipText() {
    return "The report field prefix used in the report.";
  }

  /**
   * Sets the key in the meta-data containing the label.
   *
   * @param value	the key
   */
  public void setLabelKey(String value) {
    m_LabelKey = value;
    reset();
  }

  /**
   * Returns the key in the meta-data containing the label.
   *
   * @return		the key
   */
  public String getLabelKey() {
    return m_LabelKey;
  }

  /**
   * Returns the tip text for this property.
   *
   * @return 		tip text for this property suitable for
   * 			displaying in the GUI or for listing the options.
   */
  public String labelKeyTipText() {
    return "The key in the meta-data containing the label, ignored if empty.";
  }

  /**
   * Returns a string describing the format (used in the file chooser).
   *
   * @return 			a description suitable for displaying in the
   * 				file chooser
   */
  @Override
  public String getFormatDescription() {
    return "VGG Image Annotator (VIA)";
  }

  /**
   * Returns the extension(s) of the format.
   *
   * @return 			the extension(s) (without the dot!)
   */
  @Override
  public String[] getFormatExtensions() {
    return new String[]{"json"};
  }

  /**
   * Tries to determine the parent ID for the current report.
   *
   * @param report	the report to determine the ID for
   * @return		the parent database ID, -1 if it cannot be determined
   */
  @Override
  protected int determineParentID(Report report) {
    return -1;
  }

  /**
   * Returns a new instance of the report class in use.
   *
   * @return		the new (empty) report
   */
  @Override
  public Report newInstance() {
    return new Report();
  }

  /**
   * Performs the actual reading.
   *
   * @return		the reports that were read
   */
  @Override
  protected List<Report> readData() {
    List<Report>	result;
    JSONParser 		parser;
    FileReader 		freader;
    BufferedReader 	breader;
    LocatedObject	lobj;
    LocatedObjects 	lobjs;
    JSONObject		obj;
    JSONObject		img;
    String		fname;
    JSONObject		regions;
    JSONObject		region;
    JSONObject		shapeAtts;
    JSONObject		regionAtts;
    String		label;
    String		shapeName;
    JSONArray		pointsX;
    JSONArray		pointsY;
    int			i;
    Polygon		polygon;
    int[]		x;
    int[]		y;
    Rectangle		rect;

    result  = new ArrayList<>();
    freader = null;
    breader = null;
    try {
      freader = new FileReader(m_Input.getAbsolutePath());
      breader = new BufferedReader(freader);
      parser  = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
      lobjs   = new LocatedObjects();
      obj     = (JSONObject) parser.parse(breader);
      for (String imgKey : obj.keySet()) {
	img     = (JSONObject) obj.get(imgKey);
	fname   = img.getAsString("filename");
	regions = (JSONObject) img.get("regions");
	for (String regionKey: regions.keySet()) {
	  region     = (JSONObject) regions.get(regionKey);
	  regionAtts = (JSONObject) region.get("region_attributes");
	  shapeAtts  = (JSONObject) region.get("shape_attributes");
	  shapeName  = shapeAtts.getAsString("name");
	  if (!shapeName.equals("polygon"))
	    continue;
	  // label?
	  label = null;
	  if ((regionAtts != null) && (regionAtts.get("name") != null))
	    label = "" + regionAtts.get("name");
	  // parse polygon
	  pointsX = (JSONArray) shapeAtts.get("all_points_x");
	  pointsY = (JSONArray) shapeAtts.get("all_points_y");
	  x       = new int[pointsX.size()];
	  y       = new int[pointsY.size()];
	  for (i = 0; i < pointsX.size(); i++) {
	    x[i] = ((Number) pointsX.get(i)).intValue();
	    y[i] = ((Number) pointsY.get(i)).intValue();
	  }
	  polygon = new Polygon(x, y, x.length);
	  rect    = polygon.getBounds();
	  lobj    = new LocatedObject(null, rect.x, rect.y, rect.width, rect.height);
	  lobj.setPolygon(polygon);
	  lobj.getMetaData().put("filename", fname);
	  lobj.getMetaData().put("region", regionKey);
	  if (label != null)
	    lobj.getMetaData().put(m_LabelKey, label);
	  lobjs.add(lobj);
	}
      }
      result.add(lobjs.toReport(m_Prefix));
    }
    catch (Exception e) {
      getLogger().log(Level.SEVERE, "Failed to read JSON file: " + m_Input, e);
    }
    finally {
      FileUtils.closeQuietly(breader);
      FileUtils.closeQuietly(freader);
    }

    return result;
  }
}
